
//计算列框
export const calcRowBox = (bboxes) =>{
    let minX = bboxes[0].bbox[0];
    let minY = bboxes[0].bbox[1];
    let maxX = bboxes[0].bbox[2];
    let maxY = bboxes[0].bbox[3];

    for (let i = 1; i < bboxes.length; i++) {
        minX = Math.min(minX, bboxes[i].bbox[0]);
        minY = Math.min(minY, bboxes[i].bbox[1]);
        maxX = Math.max(maxX, bboxes[i].bbox[2]);
        maxY = Math.max(maxY, bboxes[i].bbox[3]);
    }

    return [minX, minY, maxX, maxY];
}
/**
 * @description 根据bbox计算两个框是否相交
 */
export const doRectanglesIntersect =(boxA, boxB)=> {
    return (
      boxA[0] <= boxB[2] && // boxA 的 x_min 小于或等于 boxB 的 x_max
      boxA[2] >= boxB[0] && // boxA 的 x_max 大于或等于 boxB 的 x_min
      boxA[1] <= boxB[3] && // boxA 的 y_min 小于或等于 boxB 的 y_max
      boxA[3] >= boxB[1]   // boxA 的 y_max 大于或等于 boxB 的 y_min
    );
  }

/**
* @description 检查矩形 bigBox 是否完全包含矩形 smallBox
*/
export const isRectangleACompletelyContainsB = (bigBox,smallBox) => {
  // 计算两个矩形的交叉区域
  const xOverlap = Math.max(0, Math.min(bigBox[2], smallBox[2]) - Math.max(bigBox[0], smallBox[0]));
  const yOverlap = Math.max(0, Math.min(bigBox[3], smallBox[3]) - Math.max(bigBox[1], smallBox[1]));
  const overlapArea = xOverlap * yOverlap;

  // 计算矩形 smallBox 的面积
  const smallBoxArea = (smallBox[2] - smallBox[0]) * (smallBox[3] - smallBox[1]);

  // 检查交叉区域的面积是否占 smallBox 面积的60%或以上
  return overlapArea / smallBoxArea >= 0.6;
}

  /**
 * 计算相对背景图片的bbox,
 */
export const getBbox = ({top,left,width,height,scaleX,scaleY,rectScaleX,rectScaleY},bgImg) =>{
    return [
      left / scaleX - bgImg.left/ scaleX,
      top / scaleY - bgImg.top,
      (left + width * rectScaleX) / scaleX  - bgImg.left / scaleX,
      (top + height * rectScaleY) / scaleY - bgImg.top,
    ].map((item) => +item.toFixed(2));
}

  
export  const calcSize = (obj,{imgWidth,imgHeight}) =>{
    const margin = 20; // 边距
    const boxWidth = obj?.scrollWidth - (margin * 2);
    const boxHeight = obj?.scrollHeight - (margin * 2);
    // 计算保持宽高比的尺寸
    const ratio = imgWidth / imgHeight;
    let width = Math.min(boxWidth, imgWidth);
    let height = width / ratio;
    // 如果调整后的高度大于可用高度，按高度调整宽度
    if (height > boxHeight) {
        height = boxHeight;
        width = height * ratio;
    }
    return { width, height };
}




//根据字坐标按列裁切图像
export const drawImage = ({boxList,url,size}) =>  {
  return new Promise((resolve, reject) => {
    const canvas = document.createElement('canvas');
    canvas.width = size[0];
    canvas.height = size[1];
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.src = url;
    const sliceUrls = [];

    image.onload = () => {
      try {
        ctx.drawImage(image, 0, 0); // 绘制整个图像到canvas
        let posXSum = 0; //x偏移和
        // 遍历每个切片的定义
        boxList.forEach((item) => {
          const box = calcRowBox(item); // 假设这个函数返回一个包含坐标和尺寸的数组
          const slice = {
            left: box[0],
            top: box[1],
            width: box[2] - box[0], // 修复宽度计算
            height: box[3] - box[1], // 修复高度计算
          };

          // 创建一个新的canvas元素用于存储当前切片
          const sliceCanvas = document.createElement('canvas');
          sliceCanvas.width = slice.width;
          sliceCanvas.height = slice.height;

          // 获取切片canvas的上下文
          const sliceCtx = sliceCanvas.getContext('2d');

          // 从原始图片的canvas中复制当前切片到新的canvas
          sliceCtx.drawImage(canvas, slice.left, slice.top, slice.width, slice.height, 0, 0, slice.width, slice.height);

          // 将切片canvas转换为URL，并添加到结果数组中
          sliceUrls.push({
            url:sliceCanvas.toDataURL(),
            width:slice.width*0.5,
            height:slice.height*0.5,
            posX:posXSum,
          });
          posXSum+= (slice.width+10+20) // 内间距、外间距
        });
        // 当所有切片处理完成后，解决Promise
        resolve(sliceUrls);
      } catch (error) {
        // 如果发生错误，拒绝Promise
        reject(error);
      }
    };

    // 如果图像加载失败，也拒绝Promise
    image.onerror = (error) => {
      reject(error);
    };
  });
};

/**
 * @description 根据字坐标从原图裁切字图像file
 * @param {boxList} 
 * @param {url} 原图片
 * @param {size} 原图size
 * @returns 
 */
export const drawTextImageFiles = ({boxList,url,size,isFileObj}) =>{
  return new Promise((resolve, reject) => {
    const canvas = document.createElement('canvas');
    canvas.width = size[0];
    canvas.height = size[1];
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.src = url;
    const sliceUrls = [];

    image.onload = () => {
      try {
        ctx.drawImage(image, 0, 0); // 绘制整个图像到canvas
        // 遍历每个切片的定义
        const promises = boxList.map((item) => {
          console.log(item)
          const box = item.bbox; // 假设这个函数返回一个包含坐标和尺寸的数组
          const slice = {
            left: box[0],
            top: box[1],
            width: box[2] - box[0], // 宽度计算
            height: box[3] - box[1], // 高度计算
          };
          // 创建一个新的canvas元素用于存储当前切片
          const sliceCanvas = document.createElement('canvas');
          sliceCanvas.width = slice.width;
          sliceCanvas.height = slice.height;

          // 获取切片canvas的上下文
          const sliceCtx = sliceCanvas.getContext('2d');

          // 从原始图片的canvas中复制当前切片到新的canvas
          sliceCtx.drawImage(canvas, slice.left, slice.top, slice.width, slice.height, 0, 0, slice.width, slice.height);

          // 返回一个Promise，当切片canvas转换为Blob完成时解决
          return new Promise((resolve) => {
            sliceCanvas.toBlob((blob) => {
              if(isFileObj){
                // 创建一个 File 对象
               const file = new File([blob], 'image.png', {
                 type: 'image/png',
                 lastModified: Date.now()
               });
 
               // 将 File 对象添加到数组中
               sliceUrls.push({
                 file: file,
                 id: item.id
               });
             }else{
                sliceUrls.push({
                  url:URL.createObjectURL(blob),
                  id:item.id,
                });
             }
              resolve();
            }, 'image/png');
          });
        });

        // 当所有切片处理完成后，解决Promise
        Promise.all(promises).then(() => {
          resolve(sliceUrls);
        });
      } catch (error) {
        // 如果发生错误，拒绝Promise
        reject(error);
      }
    };

    // 如果图像加载失败，也拒绝Promise
    image.onerror = (error) => {
      reject(error);
    };
  });
}
//bbox是否相同
export const isSameByBbox = (a,b) =>{
  return a.findIndex((item,index)=>item!==b[index])===-1;
}

/**
 * @description 计算子元素在父元素的相对位置
 * @param {*} childBbox 
 * @param {*} parentBbox 
 * @returns 
 */
export const calcRelativePos = (childBbox, parentBbox) =>{
    // 子盒子的左上角坐标
  const childTopLeftX = childBbox[0];
  const childTopLeftY = childBbox[1];

  // 父盒子的左上角坐标
  const parentTopLeftX = parentBbox[0];
  const parentTopLeftY = parentBbox[1];

  // 子盒子左上角相对于父盒子左上角的偏移量
  const offsetX = childTopLeftX - parentTopLeftX;
  const offsetY = childTopLeftY - parentTopLeftY;

  // 返回相对位置
  return { 
    left:offsetX, 
    top:offsetY
  };
}
/**
 * @description 根据框计算文字大小
 * @param {*} bbox 
 * @param {*} styleRate 
 * @returns 
 */
export const calcTextSize = (bbox,styleRate)=>{
  const width = bbox?.[2] - bbox?.[0];
  const height = bbox?.[3] - bbox?.[1];
  let size  =width <height ? width : height;
  if(width/height > 1.2){
    size = width;
  }else if(height/width > 1.2) {
    size = height;
  }
  return size*styleRate*0.8+'px'
}


export function getImageSize(url, callback) {
  // 创建一个新的Image对象
  const img = new Image();

  // 设置跨域属性，如果需要从其他域加载图片的话
  img.crossOrigin = 'Anonymous';

  // 图片加载完毕后执行的操作
  img.onload = function() {
    // 创建一个临时的Canvas元素
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // 设置Canvas的宽高为图片的宽高
    canvas.width = img.width;
    canvas.height = img.height;
    // 调用回调函数，并传递图片的宽高
    callback(img.width, img.height);
  };

  // 处理图片加载失败的情况
  img.onerror = function(error) {
    console.error('There was an error loading the image:', error);
    callback(null, null);
  };

  // 开始加载图片
  img.src = url;

  // 如果需要立即返回宽高（而不是异步），则不能这样做，因为图片可能尚未加载完成
  // 所以我们通过回调函数异步返回结果
}
//获取缩略图
export const getUrlByImgSize = (url,size) =>{
    const [width,height] = size;
    const urlParts = url.split('/');
    const pathIndex = urlParts.findIndex(part => part === 'star3');
    const widthHeightStr = `${width}_${height}`;
    // 替换 'origin' 部分为新的宽高字符串
    urlParts[pathIndex + 1] = widthHeightStr;
    return urlParts.join('/');
}

//处理ocr参数
export const hanOcr_res = (targetData,textBoxList,isAdd) =>{
  if(isAdd){
    return textBoxList.reduce((result,row)=>{
      row.forEach((col)=>result.push({id: col.id,txt:col?.txt??'',bbox:col.bbox}))
      return result
    },[targetData])
  }
  return textBoxList.reduce((result,row)=>{
      row.forEach((col)=>{
          if(col.id !== targetData.id){
              result.push({id: col.id,txt:col?.txt??'',bbox:col.bbox}) 
          }else{
              result.push({id: targetData.id,txt:targetData?.txt??'',bbox:targetData.bbox}) 
          }
      })
      return result
  },[])
}

//处理新增/修改框体重新排版需要的参数
export const hanReLayoutParams = (targetData,textBoxList,layout,isAdd) =>{
  const msg = isAdd ? '新增字框':'修改字框';
  //在layout中找到包含targetData的列；
  const containRow = layout.find((i)=>isRectangleACompletelyContainsB(i.bbox,targetData.bbox))
  const ocr_res = hanOcr_res(targetData,textBoxList,isAdd);
  //如果layout已包含 
  if(containRow){
    console.log(msg,'layout已包含')
    return {layout,ocr_res}
  }
  //否则找到layoutX轴有交集的列
  const xList = layout.reduce((result,item,index)=>{
    if(targetData.bbox[0] <= item.bbox[2] && targetData.bbox[2] >= item.bbox[0]){
      result.push(index)
    }
    return result
  },[])
  //如果X轴方向没有重合的，则组成新列
  if(xList.length===0){
    console.log(msg,'组成新列')
    return {layout:[...layout,{bbox:targetData.bbox,category:'dan',}],ocr_res,targetData,}
  }
  //如果只有一个,则合并至列
  if(xList.length===1){
    console.log(msg,'有一个X轴重合框',xList,layout[xList[0]])
    const newBox = calcRowBox([layout[xList[0]],{bbox:targetData.bbox}]) //将box合并至重合的layout
    const newLayout = layout.toSpliced(xList[0],1,{
      bbox:newBox,
      category:layout[xList[0]].category,
    })
    return {
      layout:newLayout,
      ocr_res,
    }
  }
  console.log(msg,'有多个X轴重合框',xList)
  //如果多个，找寻找X轴交叉最多的框
  const {index:closestBboxIndex}  = xList.reduce((result,item)=>{
        const targetBbox = targetData.bbox;
        const rowBbox = layout[item].bbox;
      
        // 计算 x 轴重叠区域
        const xOverlap = Math.max(0, Math.min(targetBbox[2], rowBbox[2]) - Math.max(targetBbox[0], rowBbox[0]));
        
        // 检查是否更靠右上
        const isTopRight = rowBbox[2] > targetBbox[0] && rowBbox[1] < targetBbox[3];
      
        // 如果当前 bbox 在 x 轴上有更多的重叠区域，或者重叠区域相同但更靠右上，则更新最近 bbox
        if (xOverlap > result.xOverlap || (xOverlap === result.xOverlap && isTopRight)) {
          result = {
            xOverlap,
            isTopRight,
            index: item
          };
        }
        return result;
  },{distance:0,index:xList[0]})
  console.log(msg,'X交叉最多的layout框',closestBboxIndex,layout[closestBboxIndex])
  const newBox = calcRowBox([layout[closestBboxIndex],{bbox:targetData.bbox}])
  const newLayout = layout.toSpliced(closestBboxIndex,1,{
    bbox:newBox,
    category:layout[closestBboxIndex].category,
  })
  return {
    layout:newLayout,
    ocr_res,
  }
}


/**
 * @description 限制文本长度，包含扩展区字符（1个字占两个字符）的情况
 * @param {*} text 
 * @param {*} maxLength 
 * @returns 限制后的文本
 */
export const limitTextLength = (text, maxLength = 1) => {
  // 正则表达式匹配所有代理对字符
  const regex = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
  // 计算代理对字符的数量
  const matches = text.match(regex);
  const surrogatePairsCount = matches ? matches.length : 0;

  // 计算非代理对字符的数量
  const nonSurrogatePairsCount = text.length - surrogatePairsCount * 2;

  // 如果总字符数超过限制
  if (nonSurrogatePairsCount + surrogatePairsCount > maxLength) {
    // 截取字符串直到最大长度，保留代理对字符的完整性
    let result = '';
    let currentLength = 0;
    let index = 0;

    while (currentLength < maxLength && index < text.length) {
      const char = text[index];
      const nextChar = text[index + 1];
      const isHighSurrogate = 0xD800 <= char.charCodeAt(0) && char.charCodeAt(0) <= 0xDBFF;
      const isLowSurrogate = 0xDC00 <= nextChar.charCodeAt(0) && nextChar.charCodeAt(0) <= 0xDFFF;

      if (isHighSurrogate && isLowSurrogate) {
        // 如果是代理对字符，一起添加到结果中
        result += char + nextChar;
        index += 2; // 跳过两个字符
        currentLength += 1; // 只计算为一个字符
      } else {
        // 如果是其他字符，直接添加
        result += char;
        index += 1;
        currentLength += 1;
      }
    }
    return result;
  } 
  return text;
};


/**
 * @description 根据边界得出位置
 */
export const getValByMinMax = (min,max,now)=>{
  if(now <min){
    return min
  }else if(now >max){
    return max
  }
  return now
}

/**
 * @description 限制框只能在背景图片内
 */
export const limitBoxInBgImg = (bgImg, canvas, { startX, startY }, o) => {
  const xMin = bgImg.left;
  const xMax = bgImg.left + bgImg.width * bgImg.scaleX;
  const yMin = bgImg.top;
  const yMax = bgImg.top + bgImg.height * bgImg.scaleY;

  // 获取当前鼠标位置
  const { x, y } = canvas.getPointer(o.e);

  // 计算矩形框的新尺寸，确保不超过背景图片的边界
  let newWidth = Math.abs(startX - x);
  let newHeight = Math.abs(startY - y);

  // 限制矩形框的宽度不超过背景图片的宽度
  if (newWidth > bgImg.width * bgImg.scaleX) {
    newWidth = bgImg.width * bgImg.scaleX;
  }

  // 限制矩形框的高度不超过背景图片的高度
  if (newHeight > bgImg.height * bgImg.scaleY) {
    newHeight = bgImg.height * bgImg.scaleY;
  }

  // 计算矩形框的新位置，确保它不会超出背景图片的边界
  let newLeft = startX < x ? startX : startX - newWidth;
  let newTop = startY < y ? startY : startY - newHeight;

  // 限制矩形框的left不超过背景图片的最小x坐标
  if (newLeft < xMin) {
    newLeft = xMin;
  }

  // 限制矩形框的top不超过背景图片的最小y坐标
  if (newTop < yMin) {
    newTop = yMin;
  }

  // 限制矩形框的right不超过背景图片的最大x坐标
  const newRight = newLeft + newWidth;
  if (newRight > xMax) {
    newLeft = xMax - newWidth;
  }

  // 限制矩形框的bottom不超过背景图片的最大y坐标
  const newBottom = newTop + newHeight;
  if (newBottom > yMax) {
    newTop = yMax - newHeight;
  }

  return {
    left: newLeft,
    top: newTop,
    width: newWidth,
    height: newHeight
  };
}

/**
 * @description 控制元素只能在可见区域内
 * @param {*} element 
 * @param {*} box 
 */
export function constrainToVisibleArea(element, box,gap = 10) {
  //间隔
  // const gap = 30;
  // 获取菜单元素的当前位置
  let left = element.left + gap;
  let top = element.top;

  // 获取菜单元素的尺寸
  const width = 97;
  const height = 123;

  // 获取盒子的尺寸
  const boxWidth = box.width;
  const boxHeight = box.height;

  // 计算菜单元素在盒子上的最大可能位置
  const maxX = boxWidth - width;
  const maxY = boxHeight - height;
  // 确保菜单元素的left不会使其超出左侧边界
  if (left < 0) {
    left = 0;
  } else if (left > maxX) {
    // 确保菜单元素的left不会使其超出右侧边界
    left = maxX;
  }

  // 确保菜单元素的top不会使其超出顶部边界
  if (top < 0) {
    top = 20;
  } else if (top > maxY) {
    // 确保菜单元素的top不会使其超出底部边界
    top = maxY;
  }
  return {
    left,
    top,
  }
}

/**
 * 根据box和图片的尺寸计算等比缩放比例
 * @param {number} boxWidth - box的宽度
 * @param {number} boxHeight - box的高度
 * @param {number} imgWidth - 图片的宽度
 * @param {number} imgHeight - 图片的高度
 * @returns {number} 缩放比例
 */
export function calculateScaleRatio(boxWidth, boxHeight, imgWidth, imgHeight) {
  // 计算基于宽度的比例
  const widthRatio = boxWidth / imgWidth;
  // 计算基于高度的比例
  const heightRatio = boxHeight / imgHeight;

  // 返回两者中的较小值
  // return Math.min(widthRatio, heightRatio);
  return heightRatio
}

//计算box之间某个方向的最短距离
function calculateNearestBorderDistance(box1, box2,dir="Y") {
  // 解构赋值获取 box1 的坐标
  const [xMin1, yMin1, xMax1, yMax1] = box1;
  const [xMin2, yMin2, xMax2, yMax2] = box2;
  if(dir==="X"){
  // 计算 box1 的中心点
    const centerX1 = (xMin1 + xMax1) / 2;
  // 计算中心点到 box2 各个边的距离
    const distanceToLeft = centerX1 - xMax2;
    const distanceToRight = xMin2 - centerX1;
  // 计算绝对值并取最小值
    const distances = [
      Math.abs(distanceToLeft),
      Math.abs(distanceToRight)
    ];
    return Math.min(...distances);
  }else{
    const centerY1 = (yMin1 + yMax1) / 2;
    const distanceToTop = centerY1 - yMax2;
    const distanceToBottom = yMin2 - centerY1;
    const distances = [
      Math.abs(distanceToTop),
      Math.abs(distanceToBottom),
    ];
    return Math.min(...distances);
  }
}
/**
 * @description 获取box与boxList中距离最近的box
 * @param {*} target 目标
 * @param {*} boxList box数组 
 * @param {'Y'|'X'} 计算方向
 */
export const getNearBox = (target, boxList,dir="Y") => {
  let minDistance = Infinity;
  let nearBox = null;

  // 遍历 boxList，计算每个 box 与目标 box 的距离
  for (const b of boxList) {
    const distance = calculateNearestBorderDistance(target?.bbox, b?.bbox,dir);
    // 更新最近距离和对应的 box
    if (distance < minDistance) {
      minDistance = distance;
      nearBox = b;
    }
  }

  // 返回最近的 box
  return nearBox;
};

/**
 * @description 处理新增和修改字框的排版结果
 * @param {*} isAdd 
 * @param {*} textBoxList 当前所有字框列表
 * @param {*} targetData 新增或修改字框数据
 * @param {*} res 排版结果
 * @returns 
 */
export const hanReLayoutRes = (isAdd,textBoxList,targetData,res) =>{
  const layoutRes = res.data.detail[0].row;
  //如果是修改则需要过滤掉修改字框
  // const filterList = isAdd ? textBoxList : textBoxList.map((row,rowIndex)=>{
  //     if(rowIndex!==targetData.row) return row
  //     return row.filter(i=>i.id !== targetData.id)
  // })
  const filterList = textBoxList.reduce((result,row)=>{
    const filterRes = row.filter(i=>i.id !== targetData.id)
    if(filterRes.length!==0){
      result.push(filterRes)
    }
    return result;
  },[])
  //在新排版结果中找到该字的位置
  const {newRowIndex,newColIndex} = layoutRes.reduce((result,row,rowIndex)=>{
      const index = row.findIndex((item)=>item.id === targetData.id);
      if(index!==-1){
          result = {newRowIndex:rowIndex,newColIndex:index}
      }
      return result;
  },{})
  //在新排版结果中该字所处的列
  const newRowArr = layoutRes[newRowIndex];
  if(newRowArr.length===1){ //如果该字框所在列只有一个字框，则组成新列  
    if(newRowIndex===0) return filterList.toSpliced(0,0,[targetData]); 
    const nearId = layoutRes[newRowIndex - 1].at(-1).id;
    const res = filterList.reduce((result,item)=>{
      result.push(item);
      if(item.find(i=>i.id===nearId)){
        result.push([targetData]);
      }
      return result;
    },[])
    return res
  }else{//否则在现在的字列表去找到相邻字框，然后放到相邻字框之前或之后；
    let nearId;//相邻字框id;
    let beforeFlag;//是否放在相邻字框之前
    if(newColIndex===0){ //如果为新增字框第一个则相邻字框索引为1
      nearId = newRowArr[1].id;
      beforeFlag= true;
    }else if(newColIndex===newRowArr.length - 1){//如果为新增字框最后一个则相邻字框索引为长度-1
      nearId = newRowArr[newColIndex-1].id;
      beforeFlag= false;
    }else{//否则相邻字框则为前后离新增字框最近的框；
      const beforeItem = newRowArr[newColIndex-1];
      const afterItem = newRowArr[newColIndex+1];
      const res = getNearBox(targetData,[beforeItem,afterItem],'Y');
      nearId = res.id;
      beforeFlag = res.id === afterItem.id;
    }
    const newRes = filterList.map((row) => {
      const index = row.findIndex((item) => item.id === nearId);
      if (index === -1) return row;
      if (beforeFlag) {
        return row.toSpliced(index, 0, targetData);
      } else {
        return row.toSpliced(index + 1, 0, targetData);
      }
    });
    return newRes;
  }
}

/**
 * @description 处理排版参数
 */
export const hanLayoutParams = ({layout,ocr_res,targetRect}) =>{
  // return layout;
  const {maxY,headArr,otherArr} = layout.reduce((result,item)=>{
    if(item.category==='head'){
      result.maxY = Math.max(result.maxY,item.bbox[3]);
      result.headArr.push({
        ...item,
        index:result.headArr.length,
      });
    }else{
      result.otherArr.push(item);
    }
    return result;
  },{maxY:-1,headArr:[],otherArr:[]})
  if(headArr.length===0)return layout //如果没有抬头
  // if(targetRect.bbox[3]>maxY)return layout //如果目标框在maxY下面，则不需要计算
  const newLayout = ocr_res.reduce((result,item)=>{
      if(item.bbox[3]>maxY)return result;
      const nearBox = getNearBox(item,result,'X');
      result[nearBox.index].bbox = calcRowBox([item,nearBox]);
      return result;
    },headArr);
  if(targetRect.bbox[3]<=maxY)return newLayout;
  return [...(newLayout||[]),...(otherArr||[])]; 
}